#ifndef XLCOMM_NET_HTTPCONTEXT_H_
#define XLCOMM_NET_HTTPCONTEXT_H_

#include <algorithm>
#include <string>

#include "xlcomm/copyable.h"
#include "xlcomm/net/buffer.h"
#include "xlcomm/net/http/http_request.h"

namespace xlcomm {
namespace net {

class HttpContext : copyable {
 public:
  enum HttpRequestParseState {
    kExpectRequestLine,
    kExpectHeaders,
    kExpectBody,
    kGotAll,
  };

  HttpContext() : parse_state_(kExpectRequestLine) {}

  bool GotAll() const { return parse_state_ == kGotAll; }

  void Reset() {
    parse_state_ = kExpectRequestLine;
    HttpRequest req;
    std::swap(request_, req);
  }

  bool ReadToRequest(Buffer* buff) {
    LOG_DEBUG << "request--------\n"
              << std::string(buff->ReaderPtr(), buff->ReadableBytes());
    bool has_more = true;
    while (has_more) {
      LOG_TRACE << "----has_more";
      const char* crlf = buff->FindCrLf();
      if (crlf == nullptr) {
        parse_state_ = kExpectRequestLine;
        return true;
      }

      LOG_TRACE << "----FindCrLf";
      if (parse_state_ == kExpectHeaders && crlf == buff->ReaderPtr()) {
        LOG_TRACE;
        buff->HasRead(2);
        parse_state_ = kExpectBody;
      }

      if (parse_state_ == kExpectRequestLine) {
        LOG_TRACE << "----kExpectRequestLine";
        if (!ParseRequestLine(buff->ReaderPtr(), crlf)) return false;
        buff->HasReadTo(crlf + 2);
        parse_state_ = kExpectHeaders;
      } else if (parse_state_ == kExpectHeaders) {
        LOG_TRACE << "----kExpectHeaders";
        if (!ParseHeader(buff->ReaderPtr(), crlf)) return false;
        buff->HasReadTo(crlf + 2);
      } else if (parse_state_ == kExpectBody) {
        LOG_TRACE << "----kExpectBody";
        if (buff->ReadableBytes() >= request_.body_len_) {
          request_.body_ = buff->ReadString(request_.body_len_);
          LOG_TRACE << "HttpRequest body->" << request_.body_;
          parse_state_ = kGotAll;
        } else {
          LOG_TRACE;
        }
        return true;
      }

      if (buff->ReadableBytes() == 0) has_more = false;
    }

    return true;
  }

  const HttpRequest& request() const { return request_; }

 private:
  bool ParseRequestLine(const char* begin, const char* end) {
    LOG_TRACE << "----in";
    const char* space = std::find(begin, end, ' ');
    if (space == end) return false;
    LOG_TRACE << "HttpRequest method->" << std::string(begin, space);
    if (!SetMethod(begin, space)) return false;

    const char* url = space + 1;
    const char* space2 = std::find(url, end, ' ');
    if (space2 == end) return false;
    LOG_TRACE << "HttpRequest url->" << std::string(url, space2);
    if (!ParseUrl(url, space2)) return false;

    std::string version(space2 + 1, end);
    LOG_TRACE << "HttpRequest version->" << version;
    if (version == "HTTP/1.0")
      request_.version_ = HttpRequest::kHttp10;
    else if (version == "HTTP/1.1")
      request_.version_ = HttpRequest::kHttp11;
    else {
      request_.version_ = HttpRequest::kUnknown;
      return false;
    }

    return true;
  }

  bool ParseHeader(const char* begin, const char* end) {
    LOG_TRACE << "HttpRequest header->" << std::string(begin, end);
    const char colon[] = ": ";
    const char* pos = std::search(begin, end, colon, colon + 2);
    if (pos == end) return false;
    std::string key(begin, pos);
    LOG_TRACE << "----key=" << key;

    pos += 2;
    while (pos != end) {
      if (*pos == ' ')
        ++pos;
      else
        break;
    }
    std::string value(pos, end);
    while (value[value.size() - 1] == ' ') {
      value.resize(value.size() - 1);
    }
    request_.headers_[key] = value;
    LOG_TRACE << "----value=" << value;

    if (key == "Content-Length") request_.body_len_ = std::stoi(value);
    return true;
  }

  bool SetMethod(const char* begin, const char* end) {
    std::string m(begin, end);
    if (m == "GET")
      request_.method_ = HttpRequest::kGet;
    else if (m == "POST")
      request_.method_ = HttpRequest::kPost;
    else if (m == "HEAD")
      request_.method_ = HttpRequest::kHead;
    else if (m == "PUT")
      request_.method_ = HttpRequest::kPut;
    else if (m == "DELTE")
      request_.method_ = HttpRequest::kDelete;
    else {
      request_.method_ = HttpRequest::kInvalid;
      return false;
    }
    return true;
  }

  bool ParseUrl(const char* begin, const char* end) {
    if (begin > end) return false;
    const char* question = std::find(begin, end, '?');
    if (question == end) {
      request_.path_.assign(begin, end);
    } else {
      request_.path_.assign(begin, question);
      request_.query_.assign(question + 1, end);
    }
    return true;
  }

  HttpRequest request_;
  HttpRequestParseState parse_state_;
};

}  // namespace net
}  // namespace xlcomm

#endif  // XLCOMM_NET_HTTPCONTEXT_H_